package com.lbs;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.itextpdf.text.BadElementException;
import com.lbs.core.server.FactPdfV3;
import com.lbs.core.shared.tables.AbstractData;
import com.lizbet.server.LbsCrypt;
import com.lizbet.server.XmlFact;
import com.lizbet.server.XmlRaw;
import com.lizbet.server.myXmlParsingException;

/**
 * Contiene los metodos y propiedades basicas de un Comprobante Fiscal Digital.
 * De este parten los CFD  y CFDI
 * 
 * @author Alberto Sandoval Sotelo Domingo 26/Agosto/2012
 *
 */
public class LbsDocumento extends LbsAbstractable{
	
	//Datos generales del documento
	protected Date fecha = new Date();
	protected String serFolio;
	protected String numFolio;
	protected String moneda;
	protected Double tc;
	protected String formaDePago;
	protected String metodoPago;
	protected String noCuenta;
	protected String condicionesDePago;
	protected AbstractData tipoCfd = new AbstractData();
	protected String tipoComprobante;
	protected Double subTotal = 0d;
	protected Double iva = 0d;
	protected Double total = 0d;
	protected String noCertificado;
	protected String certificado;
	protected String observaciones;
	protected String noAprob;
	protected String anoAprob;

	//Datos de la empresa emisora
	protected AbstractData empresa;
	
	//Datos del cliente
	protected AbstractData partner = new AbstractData();
	protected AbstractData dirPartner;
	
	//Datos del cliente
	protected List<AbstractData> factPart = new ArrayList<AbstractData>();
	
	
	private AbstractData emisor = new AbstractData();
	private String sello;
	private List<AbstractData> regimenes = new ArrayList<AbstractData>();

	private FactPdfV3 pdf;
	protected XmlFact xml;
	protected String pathPlantilla;
	protected String pathBgImage;
	
	
	/**
	 * Constructor por default.
	 */
	public LbsDocumento(){
		empresa = emisor;
		empresa.add("regimenes", regimenes);
		empresa.add("tipoCfd", tipoCfd);
	}
	
	
	
	/**
	 * Se encarga de sellar el documento, crear la cadena original y establecer el 
	 * No. de certificado
	 * 
	 * @param crypt Objeto creado a partir del Certificado de Sello Digital, con el cual
	 * Se sellaran las facturas
	 * 
	 * @throws myXmlParsingException Errores al parsear la p 
	 * @throws LbsException La excepcion describe el error.
	 * @throws IOException 
	 * @throws MalformedURLException 
	 * @throws BadElementException 
	 */
	public void sellar(LbsCrypt crypt) throws myXmlParsingException, LbsException, BadElementException, MalformedURLException, IOException{
		if (!crypt.validarCertificado())
			throw new LbsException("Error: el certificado se encuentra vencido.");
		if (regimenes.size()==0)
			throw new LbsException("Error: se debe agregar al menos un regimen fiscal.");
		if (factPart.size()==0)
			throw new LbsException("Error: se debe agregar al menos una partida.");
		if (numFolio==null || numFolio.isEmpty())
			throw new LbsException("Error: se debe espesificar al menos la parte numerica del folio (numFolio).");
		if (formaDePago==null || formaDePago.isEmpty())
			throw new LbsException("Error: se debe espesificar la Forma de Pago.");
		if (metodoPago==null || metodoPago.isEmpty())
			throw new LbsException("Error: se debe espesificar el Medoto de Pago.");
		if (tipoComprobante==null || tipoComprobante.isEmpty())
			throw new LbsException("Error: se debe espesificar el Tipo de Comprobante (ingreso, egreso, traslado).");
		if (partner.get("nombre")==null || partner.get("rfc")==null || partner.getString("nombre").isEmpty() || dirPartner==null)
			throw new LbsException("Error: se deben agregar los datos completos del cliente (Nombre, RFC y direccion).");
		if (emisor.get("nombre")==null || emisor.get("rfc")==null || emisor.getString("nombre").isEmpty())
			throw new LbsException("Error: se deben agregar los datos completos de la empresa (Nombre y RFC).");

		
		noCertificado=crypt.getCertNo();
		certificado=crypt.getB64Certificado();
		
		//Se construlle el XML
		xml = new XmlFact(getAsAbstractData());
		sello=crypt.sellarCfd(xml.getCadenaOriginal().getBytes("UTF8"));
		xml.setSello(sello);
		
	}
	
	
	public void buildPdf() throws myXmlParsingException, BadElementException, MalformedURLException, IOException{
		//Construccion del PDF
		XmlRaw plantilla = new XmlRaw(pathPlantilla);
		pdf = new FactPdfV3(getAsAbstractData(),xml,plantilla,pathBgImage);
	}
	
	
	
	public byte[] getPdfBytes(){
		return pdf.dataArray.toByteArray();
	}
	
	
	public void guardarPdf(String ruta) throws IOException{
		FileOutputStream fos = new FileOutputStream(ruta);
		fos.write(getPdfBytes());
		fos.close();
	}
	
	
	public void guardarXml(String ruta) throws LbsException, IOException{
		if (xml==null)
			throw new LbsException("No se ha sellado el documento.");
		xml.saveXML(ruta);
	}
	
	
	/**
	 * Agrega un regimen fiscal de la empresa emisora al documento.
	 * 
	 * @param regimen El régimen como cadena.
	 */
	public void agregarRegimen(String regimen){
		AbstractData reg = new AbstractData();
		reg.add("regimen", regimen);
		regimenes.add(reg);
	}
	
	
	
	/**
	 * Se establece el nombre y RFC de la empresa que emite el CFD o CFDI.
	 * El RFC no debe contener guines ni espacios, ni ningun otro caracter que no pertenesca al RFc y debe ser de 12 o 13 caracteres de longitud.
	 * 
	 * @param nombre Nombre o razon social del emisor
	 * @param rfc R.F.C. del emisor.
	 * @throws LbsException 
	 */
	public void setEmisor(String nombre, String rfc) throws LbsException{
		if (rfc.length()<12 || rfc.length()>13)
			throw new LbsException("El RFC debe ser de doce o trece caracteres");
		
		emisor.add("nombre", nombre);
		emisor.add("rfc", rfc);
	}
	
	/**
	 * Establede la direccion fiscal del emisor del CFD o CFDI
	 * 
	 * @param dir Direccion.
	 * @throws LbsException 
	 */
	public void setDirFiscEmisor(LbsDireccion dir) throws LbsException{
		dir.test();
		emisor.add("dirFisc", dir.getAsAbstractData());
	}
	
	/**
	 * Establede la direccion de expedicion, (si se factura en una sucursal con direccion distinta a la fiscal) del emisor del CFD o CFDI
	 * 
	 * @param dir Direccion.
	 * @throws LbsException 
	 */
	public void setDirExpEmisor(LbsDireccion dir) throws LbsException{
		dir.test();
		empresa = new AbstractData();
		empresa.add("empresaMadre", emisor);
		empresa.add("dirFisc", dir.getAsAbstractData());
	}
	
	
	
	
	/**
	 * Se establece el nombre y RFC del cliente o receptor.
	 * El RFC no debe contener guines ni espacios, ni ningun otro caracter que no pertenesca al RFc y debe ser de 12 o 13 caracteres de longitud.
	 * 
	 * @param nombre Nombre o razon social del receptor
	 * @param rfc R.F.C. del receptor.
	 * @throws LbsException 
	 */
	public void setReceptor(String nombre, String rfc) throws LbsException{
		if (rfc.length()<12 || rfc.length()>13)
			throw new LbsException("El RFC debe ser de doce o trece caracteres");
		
		partner.add("nombre", nombre);
		partner.add("rfc", rfc);
	}
	
	/**
	 * Establece la direccion fiscal del emisor del CFD o CFDI
	 * 
	 * @param dir Direccion.
	 * @throws LbsException 
	 */
	public void setDirFiscReceptor(LbsDireccion dir) throws LbsException{
		dir.test();
		dirPartner = dir.getAsAbstractData();
	}


	/**
	 * Agrega una partida al documento que se esta creando.
	 * 
	 * @param canditad Cantidad de unidades de venta.
	 * @param uMedida	Unidad de medida
	 * @param codigo	Codigo del articulo (Opcional)
	 * @param descripcion Descripcion de la venta
	 * @param precioUnitario	Precio unitario sin IVA
	 * @param tasaIva Tasa de impuesto IVA, en formato decimal (0.16 para el 16%)
	 * @param cuentaPredial		Numero de cuenta predial (Si apalica)
	 * @throws LbsException 
	 */
	public void agregarPartida(Double cantidad, String uMedida, String codigo, String descripcion, Double precioUnitario, Double tasaIva, String cuentaPredial) throws LbsException{
		
		if (cantidad==null)
			throw new LbsException("Cantidad no puede ser null.");
		if (uMedida==null)
			throw new LbsException("uMedida no puede ser null.");
		if (descripcion==null)
			throw new LbsException("Descripcion no puede ser null.");
		if (precioUnitario==null)
			throw new LbsException("Precio unitario no puede ser null.");
		if (tasaIva==null)
			throw new LbsException("Tasa iva no puede ser null.");

		
		AbstractData part = new AbstractData();

		part.add("cantidad", cantidad);
		part.add("medida", uMedida);
		part.add("codigo", codigo);
		part.add("desc", descripcion);
		part.add("impUnit", precioUnitario);
		part.add("impTot", precioUnitario*cantidad);
		part.add("tasa", tasaIva);
		part.add("iva", tasaIva*precioUnitario*cantidad);
		part.add("cuentaPredial", cuentaPredial);
		factPart.add(part);
		
		subTotal+=precioUnitario*cantidad;
		iva+=tasaIva*precioUnitario*cantidad;
		total+=(tasaIva+1)*precioUnitario*cantidad;
	}

	
	
	public Date getFecha() {
		return fecha;
	}
	public void setFecha(Date fecha) {
		this.fecha = fecha;
	}
	public String getSerFolio() {
		return serFolio;
	}
	public void setSerFolio(String serFolio) throws LbsException {
		if (serFolio.isEmpty())
			throw new LbsException("La serie no puede ser una cadena vacia. En caso de no existir, no la espesifique o utilice null");
		
		this.serFolio = serFolio;
	}
	public String getNumFolio() {
		return numFolio;
	}
	public void setNumFolio(String numFolio) throws LbsException {
		try{
			Integer.parseInt(numFolio);
		}catch (Exception e){
			throw new LbsException("El folio solo debe contener digitos numericos.");
		}
		
		this.numFolio = numFolio;
	}
	public String getFormaDePago() {
		return formaDePago;
	}
	public void setFormaDePago(String formaDePago) {
		this.formaDePago = formaDePago;
	}
	public String getMetodoPago() {
		return metodoPago;
	}
	public void setMetodoPago(String metodoPago) {
		this.metodoPago = metodoPago;
	}
	public String getNoCuenta() {
		return noCuenta;
	}
	public void setNoCuenta(String noCuenta) {
		this.noCuenta = noCuenta;
	}
	public String getCondicionesDePago() {
		return condicionesDePago;
	}
	public void setCondicionesDePago(String condicionesDePago) {
		this.condicionesDePago = condicionesDePago;
	}
	public String getTipoComprobante() {
		return tipoComprobante;
	}
	public void setTipoComprobante(String tipoDeComprobante) {
		this.tipoComprobante = tipoDeComprobante;
	}
	public String getNoCertificado() {
		return noCertificado;
	}
	public String getSello() {
		return sello;
	}
	public String getMoneda() {
		return moneda;
	}
	public void setMoneda(String moneda) {
		this.moneda = moneda;
	}
	public Double getTc() {
		return tc;
	}
	public void setTc(Double tc) {
		this.tc = tc;
	}
	public String getObservaciones() {
		return observaciones;
	}
	public void setObservaciones(String observaciones) {
		this.observaciones = observaciones;
	}	
	
	
	public void setTel(String tel){
		empresa.add("tel1", tel);
	}
	public void setEmail(String email){
		empresa.add("email", email);
	}
	
	
	public String getCadenaOriginal(){
		if (xml==null)
			throw new RuntimeException("Debe sellar para poder obtener este dato");
		
		return xml.getCadenaOriginal();
	}
}
